const { resolve, join } = require("node:path");
const { existsSync } = require("node:fs");
const { readFile, readdir } = require("node:fs/promises");
const { exec } = require("node:child_process");

async function readJson(filePath) {
  return JSON.parse(await readFile(filePath, "utf-8"));
}

function topologicalSort(graph) {
  let result = [], // 结果数组
    visited = {}, // 已访问节点集合
    temp = {}; // 临时节点集合，用于检测环

  // 深度优先搜索
  function visit(node) {
    if (temp[node]) {
      throw new Error("存在环，无法进行拓扑排序");
    }
    if (!visited[node]) {
      temp[node] = true;
      let neighbors = graph[node];
      for (let i = 0; i < neighbors.length; i++) {
        visit(neighbors[i]);
      }
      visited[node] = true; // 标记为已访问
      temp[node] = false; // 从临时集合中移除
      result.unshift(node); // 加入结果集
    }
  }

  // 对每个节点调用visit函数
  Object.keys(graph).forEach(visit);

  return result;
}

async function getBuildOrder() {
  const packagesPath = resolve(__dirname, "..", "packages");
  const packages = new Map();
  for (const dir of await readdir(packagesPath, { withFileTypes: true, recursive: false })) {
    const packagePath = join(packagesPath, dir.name, "package.json");
    if (!existsSync(packagePath)) continue;
    const packageJson = await readJson(packagePath);
    if (packageJson && packageJson.scripts && packageJson.scripts.build) {
      packages.set(dir.name, packageJson);
    }
  }
  const graph = Object.fromEntries(Array.from(packages.keys()).map((name) => [name, []]));
  packages.forEach((packageJson, name) => {
    const dependencies = Object.keys(packageJson.dependencies || {}).concat(
      Object.keys(packageJson.devDependencies || {})
    );
    dependencies.forEach((dep) => {
      if (packages.has(dep)) {
        graph[name].push(dep);
      }
    });
  });
  return topologicalSort(graph).reverse();
}

async function execCmd(cmd) {
  return new Promise((resolve, reject) => {
    const child = exec(cmd, function (error) {
      if (error) {
        reject(error);
      } else {
        resolve();
      }
    });
    child.stdout.pipe(process.stdout);
    child.stderr.pipe(process.stderr);
  });
}

module.exports = { getBuildOrder, execCmd };
